﻿#include "SkinedDialog.h"

#include <QApplication>
#include <QDesktopWidget>
#include <QMouseEvent>
#include <QPainter>
#include <QVBoxLayout>

SkinedDialog::SkinedDialog(bool resizable, QWidget* parent)
    : QDialog(parent)
    , m_is_moving(false)
    , m_is_resizing(false)
    , m_cur_changed(false)
    , m_cur_shape(0)
    , m_resizable(resizable)
    , m_background("") {
    // init the frame proterties
    // 1、去掉边框，从而实现整个界面的自绘
    // 2、要有最大化和最小按钮。特别是最小按钮，如果没有的话，在Windows任务栏上点击时，程序不会被最小化
    // 注意，这里有个Qt的BUG，要去掉边框，就必须重写showEvent方法
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint |
                   Qt::WindowMaximizeButtonHint);

    setMouseTracking(true);
}

SkinedDialog::~SkinedDialog() {}

int SkinedDialog::getTitleHeight() {
    return 20;
}

void SkinedDialog::setBackgroundPic(QString pic_name) {
    m_background = pic_name;
    repaint();
}

void SkinedDialog::showEvent(QShowEvent* e) {
    // QT的刷新机制，有时会不刷新的bug解决办法；
    // 2022.3.29 不清楚现在还有没有这个BUG，先留着吧，应该没坏处
    setAttribute(Qt::WA_Mapped);
    QDialog::showEvent(e);
}

void SkinedDialog::paintEvent(QPaintEvent*) {
    // 填充背景，并在最外围以黑色描一个简单的边框
    QPainter painterFrame(this);
    painterFrame.setRenderHint(QPainter::Antialiasing, true);
    painterFrame.setPen(QPen(Qt::black, 1));

    // 如果设置了背景图，就用背景图做为画刷
    if (!m_background.isEmpty()) {
        painterFrame.setBrush(QBrush(QPixmap(m_background).scaled(size())));
    }

    // 绘制边框
    painterFrame.drawRect(0, 0, width(), height());
}

void SkinedDialog::mousePressEvent(QMouseEvent* event) {
    // 只能是鼠标左键移动和改变大小
    if (event->button() == Qt::LeftButton) {
        // 鼠标在箭头状态下，是移动窗口，否则是改变窗口大小
        m_is_moving   = !m_cur_changed;
        m_is_resizing = m_cur_changed;

        // 记录鼠标按下是，窗口左上角的位置
        // 左上角的位置 = 鼠标在屏幕的位置 - 鼠标在窗体内的位置
        m_move_point = event->globalPos() - pos();
    }
}

void SkinedDialog::mouseReleaseEvent(QMouseEvent* event) {
    if (event->button() == Qt::LeftButton) {
        m_is_moving   = false;
        m_is_resizing = false;

        // 左键松开时，无论在干什么，鼠标指针改回正常的箭头形状
        m_cur_changed = setCursorShape(-1);
    }
}

void SkinedDialog::mouseMoveEvent(QMouseEvent* event) {
    // 窗口最大化时，不能移动，也不能改变窗口大小
    if (isMaximized())
        return;

    // 如果正在移动窗口
    if (m_is_moving) {
        // 移动窗口到鼠标当前位置（要减去按下时记录的窗口左上角位置，否则窗口左上角会跑到当前鼠标的位置）
        move(event->globalPos() - m_move_point);
        return;
    }

    if (m_resizable) {
        // 允许通过鼠标改变窗口大小
        if (m_is_resizing) {
            // 如果正在改变窗口大小
            changeRectSize(m_cur_shape, event->pos());
        } else {
            // 如果没有正在改变窗口大小，那就判断一下鼠标是否移动到窗口边缘（在边缘上时，需要改变鼠标指针形状）
            m_cur_shape   = getSideType(event->pos());
            m_cur_changed = setCursorShape(m_cur_shape);
        }
    }
}

void SkinedDialog::mouseDoubleClickEvent(QMouseEvent* event) {
    // 标题栏的高度
    int height = getTitleHeight();

    // 如果是双击的是左键，并且在标题栏高度以内，就切换最大化状态
    if (event->button() == Qt::LeftButton && event->y() <= height && m_resizable) {
        changeMaxStatus();
    }
}

int SkinedDialog::getSideType(QPoint point) {
    if (!m_resizable) {
        return -1;
    }

    QRect rt = this->rect();

    int lenth = 2;   // 设置判断指针的距离
    if (point.x() <= rt.left() + lenth) {
        if (point.y() >= rt.bottom() - lenth) {
            return LEFTBOTTOM;
        } else if (point.y() <= rt.top() + lenth) {
            return LEFTTOP;
        }
        return LEFT;
    } else if (point.x() >= rt.right() - lenth) {
        if (point.y() >= rt.bottom() - lenth) {
            return RIGHTBOTTOM;
        } else if (point.y() <= rt.top() + lenth) {
            return RIGHTTOP;
        }
        return RIGHT;
    } else if (point.y() <= rt.top() + lenth) {
        return TOP;
    } else if (point.y() >= rt.bottom() - lenth) {

        return BOTTOM;
    }
    return -1;
}

bool SkinedDialog::setCursorShape(int flag) {
    QCursor curType;
    bool    shapeChanged = false;
    switch (flag) {
    case LEFT:
    case RIGHT:
        curType      = Qt::SizeHorCursor;
        shapeChanged = true;
        break;

    case TOP:
    case BOTTOM:
        curType      = Qt::SizeVerCursor;
        shapeChanged = true;
        break;
    case LEFTTOP:
    case RIGHTBOTTOM:
        curType      = Qt::SizeFDiagCursor;
        shapeChanged = true;
        break;
    case RIGHTTOP:
    case LEFTBOTTOM:
        curType      = Qt::SizeBDiagCursor;
        shapeChanged = true;
        break;
    default:
        curType = Qt::ArrowCursor;
        break;
    }

    this->setCursor(curType);

    return shapeChanged;
}

void SkinedDialog::changeRectSize(int flag, QPoint globalPos) {
    if (!m_resizable)
        return;

    globalPos    = mapToParent(globalPos);
    int globalX  = globalPos.x();
    int globalY  = globalPos.y();
    int widgetX  = this->pos().x();
    int widgetY  = this->pos().y();
    int LMLenght = widgetX + this->width() - globalX;   // 左移距离
    int RMLenght = globalX - widgetX + 1;
    int TMLenght = widgetY + this->height() - globalY;
    int BMLenght = globalY - widgetY + 1;

    // 防止向右最小时窗口移动
    if (LMLenght <= minimumWidth()) {
        globalX = this->pos().x();
    }

    // 防止向下最小时窗口时移动
    if (TMLenght <= minimumHeight()) {
        globalY = this->pos().y();
    }

    switch (flag) {
    case LEFT:
        setGeometry(globalX, widgetY, LMLenght, this->height());
        break;

    case RIGHT:
        setGeometry(widgetX, widgetY, RMLenght, this->height());
        break;

    case TOP:
        setGeometry(widgetX, globalY, this->width(), TMLenght);
        break;

    case BOTTOM:
        setGeometry(widgetX, widgetY, this->width(), BMLenght);
        break;

    case LEFTTOP:
        setGeometry(globalX, globalY, LMLenght, TMLenght);
        break;
    case LEFTBOTTOM:
        setGeometry(globalX, widgetY, LMLenght, BMLenght);
        break;

    case RIGHTTOP:
        setGeometry(widgetX, globalY, RMLenght, TMLenght);
        break;

    case RIGHTBOTTOM:
        setGeometry(widgetX, widgetY, RMLenght, BMLenght);
        break;

    default:
        break;
    }
}

void SkinedDialog::changeMaxStatus() {
    if (!m_resizable)
        return;

    if (isMaximized()) {
        // 当前是最大化，需要恢复到最大化前大小
        showNormal();
        emit normaled();
    } else {
        // 当前是正常状态，记录当前窗口大小，然后设置为最大化
        showMaximized();
        emit maximized();
    }
}

void SkinedDialog::reject() {
    // close();
}